using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;

namespace WebServiceClientGenerator
{
    /// <summary>
    /// Abstract class that implements the <see cref="ICodeWriter"/> and
    /// provides the common methods for code generating.
    /// </summary>
    public abstract class AbstractGenerator : ICodeWriter
    {
        #region Private Instance Fields

        private CodeDomProvider _codeDomProvider;
        private CodeGeneratorOptions _codeGeneratorOptions;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets and sets the <see cref="CodeDomProvider"/>.
        /// Default is <see cref="CSharpCodeProvider"/>.
        /// </summary>
        public CodeDomProvider CodeDomProvider
        {
            get
            {
                if (_codeDomProvider == null)
                {
                    _codeDomProvider = new CSharpCodeProvider();
                }
                return _codeDomProvider;
            }
            set
            {
                CodeUtils.CheckNullSetter(value);
                _codeDomProvider = value;
            }
        }

        /// <summary>
        /// Gets and sets the <see cref="CodeGeneratorOptions"/>.
        /// </summary>
        public CodeGeneratorOptions CodeGeneratorOptions
        {
            get
            {
                if (_codeGeneratorOptions == null)
                {
                    _codeGeneratorOptions = new CodeGeneratorOptions();
                }
                return _codeGeneratorOptions;
            }
            set
            {
                CodeUtils.CheckNullSetter(value);
                _codeGeneratorOptions = value;
            }
        }

        #endregion

        /// <summary>
        /// Generate the source and write it to the <paramref name="writer"/>.
        /// </summary>
        /// <param name="writer">To where the source code is written.</param>
        public abstract void Generate(TextWriter writer);

        /// <summary>
        /// Generates the source code in memory and compare it with the content
        /// of the file specified by the <paramref name="filePath"/>. Overwrite
        /// the file only when there is difference.
        /// </summary>
        /// <param name="filePath">The file for the generated source code.</param>
        protected virtual void GenerateIfDifferent(string filePath)
        {
            FileInfo file = new FileInfo(filePath);

            // Determine if we need to overwrite the source file if one
            // already exists.
            byte[] bytes = null;
            int size = 0;
            if (file.Exists)
            {
                // Generate source code in memory
                using (MemoryStream ms = new MemoryStream())
                {
                    GenerateAndFlush(new StreamWriter(ms));
                    ms.Position = 0;
                    bytes = ms.GetBuffer();
                    size = (int)ms.Length;
                    ms.Close();
                }

                // Compare with the existing file.
                if (file.Length == size)
                {
                    byte[] old = new byte[size];
                    using (Stream s = file.OpenRead())
                    {
                        s.Read(old, 0, size);
                        s.Close();
                    }

                    // Don't overwrite if the are the same.
                    int i = size; while (--i >= 0)
                    {
                        if (old[i] != bytes[i]) break;
                    }
                    if (i < 0) return;
                }
            }

            using (Stream s = file.Create())
            {
                if (bytes != null)
                {
                    s.Write(bytes, 0, size);
                }
                else
                {
                    GenerateAndClose(new StreamWriter(s));
                }
                s.Close();
            }
        }

        private void GenerateAndClose(TextWriter writer)
        {
            using (writer)
            {
                Generate(writer);
                //Close StreamWriter
                writer.Close();
            }
        }

        private void GenerateAndFlush(TextWriter writer)
        {
            Generate(writer);
            writer.Flush();
        }
    }
}